﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics;

using System.Xml;

namespace SimpleIR
{
	public sealed class IRBasicBlock : IRT1T2Region
	{
		/// <summary>
		/// The id of the block.
		/// </summary>
		public readonly long BlockID;

		/// <summary>
		/// The list of operations in the block.
		/// </summary>
		public readonly List<IRByteCode> Operations;

		/// <summary>
		/// List of all the variable names that are in scope at the entry of the block.
		/// </summary>
		public readonly HashSet<IRVarName> InScopeAtEntry;

		/// <summary>
		/// Map from variables to their types in this block.
		/// </summary>
		public readonly Dictionary<IRVarName, IRType> LocalsToTypeMap;

		private int m_blockentryumloc;

		public IRBasicBlock(List<IRByteCode> sl, long bid, HashSet<IRVarName> inscope, Dictionary<IRVarName, IRType> localtypes)
		{
			this.BlockID = bid;

			this.Operations = sl;

			this.InScopeAtEntry = inscope;
			this.LocalsToTypeMap = localtypes;

			this.m_blockentryumloc = -1;
		}

		/// <summary>
		/// Set the variables that are in scope at call entry.
		/// </summary>
		public void SetInScopeVars(List<IRVarName> inscope, Dictionary<IRVarName, IRType> localtotypemap)
		{
			this.InScopeAtEntry.UnionWith(inscope);

			foreach(KeyValuePair<IRVarName, IRType> ltye in localtotypemap)
				this.LocalsToTypeMap.Add(ltye.Key, ltye.Value);
		}

		public override String ToString()
		{
			String idstr = "        ";
			StringBuilder sb = new StringBuilder("        L" + this.BlockID + ":\n");
			sb.Append(idstr + "@Entry=" + this.m_blockentryumloc + "\n");

			for(int i = 0; i < this.Operations.Count; ++i)
			{
				IRByteCode istmt = this.Operations[i];

				sb.Append(idstr + istmt.ToString());
				sb.Append("   //" + istmt.GenStmtNumber() + "\n");
			}

			return sb.ToString();
		}

		public override void DGMLDisplayString(XmlWriter xwn, XmlWriter xwl, bool toplevel)
		{
			StringBuilder sb = new StringBuilder("L: " + this.BlockID + "\n");

			for(int i = 0; i < this.Operations.Count; ++i)
			{
				IRByteCode istmt = this.Operations[i];

				sb.Append(istmt.ToString());
				sb.Append("   //" + istmt.GenStmtNumber() + "\n");
			}

			xwn.WriteStartElement("Node");
			xwn.WriteAttributeString("Id", this.GenDisplayNameString());
			xwn.WriteAttributeString("Label", sb.ToString());
			xwn.WriteEndElement();

			List<uint> allbt = new List<uint>();
			this.GetAllBranchTargets(allbt);
			foreach(uint trgt in allbt)
			{
				xwl.WriteStartElement("Link");
				xwl.WriteAttributeString("Source", this.BlockID.ToString());
				xwl.WriteAttributeString("Target", trgt.ToString());
				xwl.WriteEndElement();
			}
		}

		public override string GenDisplayNameString()
		{ return this.BlockID.ToString(); }

		/// <summary>
		/// Add all the possible branch target from this block to the list.
		/// </summary>
		public void GetAllBranchTargets(List<uint> trgts)
		{
			if(this.Operations.Count != 0)
				this.Operations[this.Operations.Count - 1].GetBranchTargets(trgts);
		}

		/// <summary>
		/// Return true if this block has a single branch target at the end.
		/// </summary>
		public bool HasSingleBranchTarget
		{ get { return (this.Operations[this.Operations.Count - 1].OperationCode == OpCode.Op.BranchU); } }

		/// <summary>
		/// If this block has a unique branch target, return the target.
		/// </summary>
		public uint SingleBranchTarget
		{ 
			get 
			{
				Debug.Assert(this.HasSingleBranchTarget);
				return ((IRBranchOp)this.Operations[this.Operations.Count - 1]).BRUTarget; 
			} 
		}

		/// <summary>
		/// Add the given bytecode as the first op in the block.
		/// </summary>
		public void AddByteCodeAtStartOfBlock(IRByteCode s)
		{ this.Operations.Insert(0, s); }

		/// <summary>
		/// Add the given bytecode at the end of the block.
		/// </summary>
		public void AddByteCodeAtEndOfBlock(IRByteCode s)
		{ this.Operations.Add(s); }

		/// <summary>
		/// Add the given bytecode to the end of the block (but before the jump operation).
		/// </summary>
		public void AddByteCodeAtEndOfBlockButBeforeJump(IRByteCode s)
		{ this.Operations.Insert(this.Operations.Count - 1, s); }

		/// <summary>
		/// Add the given bytecodes to the start of the block.
		/// </summary>
		public void AddByteCodeListAtStartOfBlock(List<IRByteCode> sl)
		{ this.Operations.InsertRange(0, sl); }

		/// <summary>
		/// The use/mod id of the block (a use/mod location less than all others in the block).
		/// </summary>
		public int BlockEntryUseModLoc
		{ get { return this.m_blockentryumloc; } }

		public override int MinStmt()
		{ return this.m_blockentryumloc; }

		public override int MaxStmt()
		{ return this.Operations[this.Operations.Count - 1].UseModLoc; }

		//////////////////////////////////////////////////////////////////////////////////////////////////
		//Numbering and T1T2 operations

		public override void NumberRegion(CtrGen cg)
		{
			this.m_blockentryumloc = cg.GenNextVal();

			for(int i = 0; i < this.Operations.Count; ++i)
			{
				IRByteCode bc = this.Operations[i];
				bc.SetUseModLoc(cg);
			}
		}
	}
}
